#pragma once
#include <string>
#include <functional>
#include <thread>
#include <unordered_set>
#include <mutex>
#include "../Dispatcher.h"

struct sockaddr_in;
struct ip_mreq;
struct OverlappedData;
class UDPClient : public Dispatcher
{
 
public:
	UDPClient();
    ~UDPClient();

	bool Initialize(int port);

	void Terminate();

	void Update();

	int SendTo(const char* buffer, uint32_t len, const char* ip, int port);

	int SendTo(const char* buffer, uint32_t len, sockaddr_in* remote);

    bool GetLocalIP(std::string* outIP);

	bool GetLocalAddr(sockaddr_in* out);

    bool GetLocalPort(int* outPort);

    bool JoinMulticastGroup(const char* group);

    void DorpMulticastGroup();

	static bool BeginReceive(OverlappedData* overlappedData);

	void InsertContext(OverlappedData* data);

	void RemoveContext(OverlappedData* data);

	template <class _Fx, class _Types>
	void RegisterReceivedCallback(_Fx&& _Func, _Types&& _User) {
		onRecv = std::bind(_STD forward<_Fx>(_Func), _User, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
	}
	template <class _Fx>
	void RegisterReceivedCallback(_Fx&& _Func) {
		onRecv = std::bind(_STD forward<_Fx>(_Func), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
	}
	void UnregisterReceivedCallback() {
		onRecv = nullptr;
	}

	template <class _Fx, class _Types>
	void RegisterInsertCallback(_Fx&& _Func, _Types&& _User) {
		insertCallback = std::bind(_STD forward<_Fx>(_Func), _User, std::placeholders::_1);
	}
	
	template <class _Fx, class _Types>
	void RegisterRemoveCallback(_Fx&& _Func, _Types&& _User) {
		removeCallback = std::bind(_STD forward<_Fx>(_Func), _User, std::placeholders::_1);
	}

	void SetCompletionPort(void* completionPort);

	void* GetRecvContext();

private:

	void RecvThread();

public:

    void* user = nullptr;

	void* completionPort = nullptr;


private:

    ip_mreq* mreq = nullptr;
    uint64_t udpSocket = 0;
	bool isJoin = false;

    std::function<void(char* buffer, int len, sockaddr_in* remote)> onRecv;

	std::function<void(OverlappedData* data)> insertCallback, removeCallback;

	OverlappedData* overlappedData = nullptr;

	std::thread* recvThread = nullptr;

	std::unordered_set<OverlappedData*> overlappedDataSet;
	std::mutex overlappedDataSetMux;
};

